/*
 * Copyright 2006-2007 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package asphalt2.factory.impl
{
	import asphalt2.factory.BeanFactory;
	
	import flash.utils.getQualifiedClassName;
	
	import mx.styles.CSSStyleDeclaration;
	import mx.styles.StyleManager;
	/**
	 * CSSで設定を行うBean生成Factory
	 * @auther c9katayama
	 */
	public class CSSBeanFactory implements BeanFactory
	{		
		private static const REF_PATTEN:RegExp = /[$][{][^)]*[}]/;
		
		private static const TYPE_CONFIG_NAME:String = "beanType";
		private static const SCOPE_CONFIG_NAME:String = "beanScope";
		private static const SCOPE_SINGLETON:String = "singleton";
		private static const SCOPE_PROTOTYPE:String = "prototype";
	
		private var singletonScopeMap:Object = {};
		
		private static var _instance:CSSBeanFactory = new CSSBeanFactory();
		
		public static function getInstance():CSSBeanFactory{
			return _instance;
		}
		/**
		 * beanNameを使用してbeanを検索し、beanインスタンスを返します。
		 */
		public function getBean(beanName:String):Object{
			return resolveBean(beanName,null);
		}
		/**
		 * Classのショートネームを使用して、beanを検索し、beanインスタンスを返します。
		 */		
		public function getBeanByType(clazz:Class):Object{
			var beanName:String = getShortClassName(clazz);
			var bean:Object = getBean(beanName);
			if(bean is clazz){
				return bean;
			}else{
				throw new Error("Type mismatch error.required type="+clazz+" ,but bean type="+bean);
			}
		}
		private function resolveBean(beanName:String,resolveScopeMap:Object):Object{
			var resolvedBean:Object = singletonScopeMap[beanName];
			if(resolvedBean == null && resolveScopeMap != null){
				resolvedBean = resolveScopeMap[beanName];
			}
			if(resolvedBean != null){
				return resolvedBean;
			}
			
			var desc:CSSStyleDeclaration = StyleManager.getStyleDeclaration(beanName);
			if(desc==null){
				desc = StyleManager.getStyleDeclaration("."+beanName);
			}
			if(desc == null){
				throw new Error("CSSStyleDeclaration not found.selector="+beanName);
			}
			var clazz:Class = Class(desc.getStyle(TYPE_CONFIG_NAME));
			if(clazz == null){
				throw new Error("[bean-type] property is not defined.");
			}
			var bean:Object = new clazz();
			
			var resolveScopeMap:Object={};
			var scope:String = desc.getStyle(SCOPE_CONFIG_NAME);
			if(scope==null||scope==SCOPE_SINGLETON){
				singletonScopeMap[beanName] = bean;
			}else{
				resolveScopeMap[beanName] = bean;
			}
			
			var propertyMap:Object = resolveProperty(desc,resolveScopeMap);
			applyProperties(clazz,bean,propertyMap);
			return bean;
		}
		private function resolveProperty(desc:CSSStyleDeclaration,resolveScopeMap:Object):Object{
			var factory:Function = desc.factory;
			var resolveProperties:Object = {};
			if (factory != null){
	            factory.prototype = {};
	            var configMap:Object = new factory();
	            for(var propertyName:String in configMap){
	            	if(propertyName != TYPE_CONFIG_NAME 
	            		&& propertyName != SCOPE_CONFIG_NAME){
		            	var value:Object = configMap[propertyName];
		            	resolveProperties[propertyName] = evaluate(value,resolveScopeMap);
		            }
	            }
	        }
	        return resolveProperties;
		}
		private function evaluate(value:Object,resolveScopeMap:Object):Object{
			if(value is String){
				var valueString:String = value as String;
				if(REF_PATTEN.test(valueString)){
					valueString = valueString.substring("${".length,valueString.length-1);
					return resolveBean(valueString,resolveScopeMap);
				}
			}else if(value is Array){
				var valueArray:Array = value as Array;
				for(var i:int = 0;i < valueArray.length;i++){
					valueArray[i] = evaluate(valueArray[i],resolveScopeMap);
				}
				return valueArray;
			}
			return value;
		}
		private function applyProperties(clazz:Class,bean:Object,propertyMap:Object):void{
			for(var propertyName:String in propertyMap){
				bean[propertyName] = propertyMap[propertyName];
			}
		}
		/**
		 * クラスのショートネームを取得します。
		 * @param clazz Class
		 * @return クラスショートネーム
		 */
		private static function getShortClassName(clazz:Class):String{
			var name:String = getQualifiedClassName(clazz);
			name = name.replace("::",".");
			var dot:int = name.lastIndexOf(".");
			if(dot!=-1){
				name = name.substring(dot+1,name.length);
			}
			return name;			
		}
	}
}